package net.sf.xoperator.blackboard;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openbbs.blackboard.BlackboardObserver;
import org.openbbs.blackboard.BlackboardZoneException;
import org.openbbs.blackboard.CloneStrategy;
import org.openbbs.blackboard.EntryFilter;
import org.openbbs.blackboard.ObjectBlackboard;
import org.openbbs.blackboard.ReadBlackboardException;
import org.openbbs.blackboard.Zone;
import org.openbbs.blackboard.ZoneSelector;
import org.openbbs.blackboard.persistence.BlackboardMemory;

public class ThreadsafeObjectBlackboard extends ObjectBlackboard {
	
		   private final Map<BlackboardObserver, ZoneSelector> observers = new HashMap<BlackboardObserver, ZoneSelector>();
		   private CloneStrategy cloneStrategy;
         private Log log = LogFactory.getLog(ThreadsafeObjectBlackboard.class);
		  
		   private BlackboardMemory memory;

		   public ThreadsafeObjectBlackboard() {
		      this(new NoCloningCloneStrategy());
		   }

		   public ThreadsafeObjectBlackboard(CloneStrategy cloneStrategy) {
		      this.setCloneStrategy(cloneStrategy);
		      this.setMemory(new ThreadsafeTransientMemory());
		      this.openZone(Zone.DEFAULT);
		   }

		   @Override
		public void setCloneStrategy(CloneStrategy cloneStrategy) {
		      Validate.notNull(cloneStrategy);
		      this.cloneStrategy = cloneStrategy;
		   }

		   @Override
		public void setMemory(BlackboardMemory memory) {
		      Validate.notNull(memory);
		      this.memory = memory;
		   }

		   @Override
		public void openZone(Zone zone) {
		      Validate.notNull(zone);

		      if (this.memory.zoneExists(zone)) {
		         throw new BlackboardZoneException("zone " + zone.name() + " is already open");
		      }
		      this.memory.createZone(zone);
		   }

		   @Override
		public void closeZone(Zone zone) {
		      Validate.notNull(zone);

		      if (!this.memory.zoneExists(zone)) {
		         throw new BlackboardZoneException("zone " + zone.name() + " is unknown");
		      }
		      this.memory.dropZone(zone);
		   }

		   @Override
		public void write(Zone zone, Object entry) {
		      try {
               Validate.notNull(entry);

               if (!this.knowsZone(zone)) throw new BlackboardZoneException("zone " + zone.name() + " is unknown");

//		      if (this.memory.entryExists(entry)) {
//		         throw new WriteBlackboardException("entry " + entry.toString() + " is already present on this blackboard");
//		      }

               Object clonedEntry = this.cloneStrategy.clone(entry);
               this.memory.storeEntry(zone, clonedEntry);

               this.notifyEntryAdded(zone, clonedEntry);
            } catch (Throwable e) {
               this.log.error("Throwable caught: " + e);
               //throw new RuntimeException(e);
            }
		   }

		   @Override
		public Zone zoneOf(Object entry) {
		      Validate.notNull(entry, "cannot determine zone of null entry");

		      Zone zone = this.memory.getZone(entry);
		      if (zone == null) {
		         throw new ReadBlackboardException("unknown entry " + entry.toString());
		      }

		      return zone;
		   }

		   @Override
		public Object read(ZoneSelector zoneSelector, EntryFilter filter) {
		      Validate.notNull(zoneSelector);
		      Validate.notNull(filter);

		      Iterator<Object> it = this.memory.getEntries(zoneSelector, filter);
		      if (!it.hasNext()) {
		         return null;
		      }

		      return this.cloneStrategy.clone(it.next());
		   }

		   @Override
		public Set<Object> readAll(ZoneSelector zoneSelector, EntryFilter filter) {
		      Validate.notNull(zoneSelector);
		      Validate.notNull(filter);

		      Set<Object> entries = new HashSet<Object>();
		      for (Iterator<Object> it = this.memory.getEntries(zoneSelector, filter); it.hasNext();) {
		         entries.add(this.cloneStrategy.clone(it.next()));
		      }

		      return entries;
		   }

		   @Override
		public boolean exists(ZoneSelector zoneSelector, EntryFilter filter) {
		      return this.read(zoneSelector, filter) != null;
		   }

		   @Override
		public Object take(ZoneSelector zoneSelector, EntryFilter filter) {
		      Validate.notNull(zoneSelector);
		      Validate.notNull(filter);

		      Iterator<Object> it = this.memory.getEntries(zoneSelector, filter);
		      if (!it.hasNext()) {
		         throw new ReadBlackboardException("no entry selected");
		      }

		      Object takenEntry = it.next();
		      Zone zoneOfTakenEntry = this.memory.getZone(takenEntry);
		      this.memory.removeEntry(takenEntry);

		      this.notifyEntryRemoved(zoneOfTakenEntry, takenEntry);

		      return takenEntry;
		   }

		   @Override
		public void registerInterest(ZoneSelector zoneSelector, BlackboardObserver observer) {
		      Validate.notNull(zoneSelector);
		      Validate.notNull(observer);
		      this.observers.put(observer, zoneSelector);
		   }

		   private boolean knowsZone(Zone zone) {
		      Validate.notNull(zone);
		      return this.memory.zoneExists(zone);
		   }

		   private void notifyEntryAdded(Zone zone, Object entry) {
		      for (BlackboardObserver observer : this.observers.keySet()) {
		         if (this.isObserverInterstedInZone(observer, zone)) observer.blackboardDidAddEntry(this, zone, entry);
		      }
		   }

		   private void notifyEntryRemoved(Zone zone, Object entry) {
		      for (BlackboardObserver observer : this.observers.keySet()) {
		         if (this.isObserverInterstedInZone(observer, zone)) observer.blackboardDidRemoveEntry(this, zone, entry);
		      }
		   }

		   private boolean isObserverInterstedInZone(BlackboardObserver observer, Zone zone) {
		      Validate.notNull(observer);
		      Validate.notNull(zone);

		      ZoneSelector zoneSelectorForObserver = this.observers.get(observer);
		      Validate.notNull(zoneSelectorForObserver, "observer " + observer + " has no ZoneSelector");
		      return zoneSelectorForObserver.selects(zone);
		   }
}
